﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Windows.Forms;
using DTO;
using System.Reflection;

namespace BUS
{
    public class CMyHook
    {

        #region Windows constants

        public event MouseEventHandler OnMouseActivity;
        public event KeyEventHandler HKeyDown;
        public event KeyPressEventHandler HKeyPress;
        public event KeyEventHandler HKeyUp;

        private int hMouseHook = 0;
        private int hKeyboardHook = 0;
        
        private static CWindowsAPI.HookProc MouseHookProcedure;
        private static CWindowsAPI.HookProc KeyboardHookProcedure;

        #endregion

        #region

        public CMyHook(Boolean MouseHook, Boolean KeyHook)
        {
            Start(MouseHook, KeyHook);
        }

        #endregion

        #region Hook

        public void Start(bool InstallMouseHook, bool InstallKeyboardHook)
        {
            if (hMouseHook == 0 && InstallMouseHook)
            {
                // Create an instance of HookProc.
                MouseHookProcedure = new CWindowsAPI.HookProc(MouseHookProc);
                //install hook
                hMouseHook = CWindowsAPI.SetWindowsHookEx(CWindowsAPI.WH_MOUSE_LL, MouseHookProcedure, IntPtr.Zero, 0);
                if (hMouseHook == 0)
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    Stop(true, false, false);
                    throw new Win32Exception(errorCode);
                }
            }

            if (hKeyboardHook == 0 && InstallKeyboardHook)
            {
                KeyboardHookProcedure = new CWindowsAPI.HookProc(KeyboardHookProc);
                hKeyboardHook = CWindowsAPI.SetWindowsHookEx(CWindowsAPI.WH_KEYBOARD_LL, KeyboardHookProcedure, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),0);
                if (hKeyboardHook == 0)
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    Stop(false, true, false);
                    throw new Win32Exception(errorCode);
                }
            }
        }

        public void Stop(bool UninstallMouseHook, bool UninstallKeyboardHook, bool ThrowExceptions)
        {
            if (hMouseHook != 0 && UninstallMouseHook)
            {
                int retMouse = CWindowsAPI.UnhookWindowsHookEx(hMouseHook);
                hMouseHook = 0;
                if (retMouse == 0 && ThrowExceptions)
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    throw new Win32Exception(errorCode);
                }
            }

            if (hKeyboardHook != 0 && UninstallKeyboardHook)
            {
                int retKeyboard = CWindowsAPI.UnhookWindowsHookEx(hKeyboardHook);
                hKeyboardHook = 0;
                if (retKeyboard == 0 && ThrowExceptions)
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    throw new Win32Exception(errorCode);
                }
            }
        }

        private int MouseHookProc(int nCode, int wParam, IntPtr lParam)
        {
            if ((nCode >= 0) && (OnMouseActivity != null))
            {
                CWindowsAPI.MouseLLHookStruct mouseHookStruct = (CWindowsAPI.MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(CWindowsAPI.MouseLLHookStruct));

                MouseButtons button = MouseButtons.None;
                short mouseDelta = 0;
                switch (wParam)
                {
                    case CWindowsAPI.WM_LBUTTONDOWN:
                        //case WM_LBUTTONUP: 
                        //case WM_LBUTTONDBLCLK: 
                        button = MouseButtons.Left;
                        break;
                    case CWindowsAPI.WM_RBUTTONDOWN:
                        //case WM_RBUTTONUP: 
                        //case WM_RBUTTONDBLCLK: 
                        button = MouseButtons.Right;
                        break;
                    case CWindowsAPI.WM_MOUSEWHEEL:
                        mouseDelta = (short)((mouseHookStruct.mouseData >> 16) & 0xffff);
                        break;
                }

                int clickCount = 0;
                if (button != MouseButtons.None)
                    if (wParam == CWindowsAPI.WM_LBUTTONDBLCLK || wParam == CWindowsAPI.WM_RBUTTONDBLCLK) clickCount = 2;
                    else clickCount = 1;

                MouseEventArgs e = new MouseEventArgs(button, clickCount, mouseHookStruct.pt.x, mouseHookStruct.pt.y, mouseDelta);
                OnMouseActivity(this, e);
            }
            return CWindowsAPI.CallNextHookEx(hMouseHook, nCode, wParam, lParam);
        }

        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            bool handled = false;
            if ((nCode >= 0) && (HKeyDown != null || HKeyUp != null || HKeyPress != null))
            {
                CWindowsAPI.KeyboardHookStruct MyKeyboardHookStruct = (CWindowsAPI.KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(CWindowsAPI.KeyboardHookStruct));
                if (HKeyDown != null && (wParam == CWindowsAPI.WM_KEYDOWN || wParam == CWindowsAPI.WM_SYSKEYDOWN))
                {
                    Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
                    KeyEventArgs e = new KeyEventArgs(keyData);
                    HKeyDown(this, e);
                    handled = handled || e.Handled;
                }

                if (HKeyPress != null && wParam == CWindowsAPI.WM_KEYDOWN)
                {
                    bool isDownShift = ((CWindowsAPI.GetKeyState(CWindowsAPI.VK_SHIFT) & 0x80) == 0x80 ? true : false);
                    bool isDownCapslock = (CWindowsAPI.GetKeyState(CWindowsAPI.VK_CAPITAL) != 0 ? true : false);

                    byte[] keyState = new byte[256];
                    CWindowsAPI.GetKeyboardState(keyState);
                    byte[] inBuffer = new byte[2];
                    if (CWindowsAPI.ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) == 1)
                    {
                        char key = (char)inBuffer[0];
                        if ((isDownCapslock ^ isDownShift) && Char.IsLetter(key)) key = Char.ToUpper(key);
                        KeyPressEventArgs e = new KeyPressEventArgs(key);
                        HKeyPress(this, e);
                        handled = handled || e.Handled;
                    }
                }

                if (HKeyUp != null && (wParam == CWindowsAPI.WM_KEYUP || wParam == CWindowsAPI.WM_SYSKEYUP))
                {
                    Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
                    KeyEventArgs e = new KeyEventArgs(keyData);
                    HKeyUp(this, e);
                    handled = handled || e.Handled;
                }

            }
            if (handled)
                return 1;
            else
                return CWindowsAPI.CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
        }

        #endregion
    }
}
